package com.eternal.base.data;


import com.clj.fastble.data.BleDevice;
import com.clj.fastble.scan.BleScanRuleConfig;
import com.eternal.base.LogService;
import com.eternal.base.R;
import com.eternal.base.data.ble.*;
import com.eternal.base.concat.*;
import com.eternal.base.data.source.IDeviceSource;
import com.eternal.base.database.entity.Device;
import com.eternal.base.database.entity.Notification;
import com.eternal.base.exception.BleException;
import com.eternal.base.exception.NoScanDevice;
import com.eternal.base.global.BluetoothKey;
import com.eternal.base.protocol.CFamilialResolution;
import com.eternal.base.protocol.ProtocolResolution;
import com.eternal.base.protocol.ProtocolTransformer;
import com.eternal.base.utils.ScanRecord;
import com.eternal.framework.bus.RxBus;
import com.eternal.framework.bus.RxSubscriptions;
import com.eternal.framework.utils.KLog;
import com.eternal.framework.utils.Utils;
import com.google.common.collect.Lists;

import org.reactivestreams.Publisher;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import io.reactivex.Completable;
import io.reactivex.CompletableEmitter;
import io.reactivex.CompletableOnSubscribe;
import io.reactivex.CompletableSource;
import io.reactivex.Flowable;
import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.ObservableSource;
import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.SingleSource;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.annotations.NonNull;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;

/**
 * Created by Administrator
 * On 2020/4/24
 * Description:
 *
 * @author Administrator
 */
public class DeviceRepository extends BaseRepository implements BleServer.DisconnectListener {
    private IDeviceSource source;
    private Map<String, String> lastTime;

    /**
     * @param source
     * @param control
     */
    DeviceRepository(IDeviceSource source, IBleControl control) {
        super(control);
        this.source = source;
        lastTime = new HashMap<>();
    }

    /**
     * @param device
     */
    @Override
    public void onDisconnect(final BleDevice device) {
        LogService.getInstance().remove(device.getMac());
        Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) throws Exception {
                source.setTime(device.getMac(), System.currentTimeMillis());
                setTime(device.getMac(), Utils.getString(R.string.tip_last,
                        ProtocolTransformer.getTime(System.currentTimeMillis())));
                RxBus.getDefault().post(new BluetoothEvent(BluetoothEvent.DISCONNECT, device.getMac()));
                KLog.i("save time");
            }
        }).subscribeOn(Schedulers.io()).subscribe();
    }

    public String getTime(String mac) {
        return lastTime.get(mac);
    }

    private void setTime(String mac, String time) {
        lastTime.put(mac, time);
    }

    public void clean() {
        control.clean();
    }

    public void disableBle() {
        LogService.getInstance().clear();
        Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) throws Exception {
                RxBus bus = RxBus.getDefault();
                long time = System.currentTimeMillis();
                for (BleDevice device : control.disableBle()) {
                    source.setTime(device.getMac(), time);
                    setTime(device.getMac(), Utils.getString(R.string.tip_last,
                            ProtocolTransformer.getTime(time)));
                    bus.post(new BluetoothEvent(BluetoothEvent.DISCONNECT, device.getMac()));
                }
            }
        }).subscribeOn(Schedulers.io()).subscribe();
    }

    public Single<List<String>> scanAndConnect() {
        BleScanRuleConfig config = new BleScanRuleConfig.Builder()
                .setAutoConnect(false).setScanTimeOut(1000).build();
        return control.scan(config).observeOn(Schedulers.io()).flatMap(new Function<List<BleDevice>, SingleSource<List<String>>>() {
            @Override
            public SingleSource<List<String>> apply(List<BleDevice> bleDevices) throws Exception {
                List<BleDevice> temp = new ArrayList<>();
                Iterator<BleDevice> iter;
                BleDevice device;
                List<String> macs = source.getAllMac();
                for (String mac : macs) {
                    iter = bleDevices.iterator();
                    while (iter.hasNext()) {
                        device = iter.next();
                        if (device.getMac().equals(mac)) {
                            temp.add(device);
                            iter.remove();
                            break;
                        }
                    }
                }
                Function<BleStatue, BleDevice> tmap = new Function<BleStatue, BleDevice>() {
                    @Override
                    public BleDevice apply(BleStatue statue) throws Exception {
                        String mac = statue.getDevice().getMac();
                        byte type = source.getType(mac);
                        if (type == BluetoothKey.DEVICE_TYPE_A || type == BluetoothKey.DEVICE_TYPE_B || type == BluetoothKey.DEVICE_TYPE_E) {
                            refreshNotifications(statue);
                        }
                        return statue.getDevice();
                    }
                };

                List<Single<BleDevice>> list = new ArrayList<>(temp.size());
                for (BleDevice d : temp) {
                    list.add(connect(d).observeOn(Schedulers.io()).map(tmap));
                }
                return Single.merge(list).toList().map(new Function<List<BleDevice>, List<String>>() {
                    @Override
                    public List<String> apply(List<BleDevice> bleDevices) throws Exception {
                        List<String> result = Lists.newArrayListWithCapacity(bleDevices.size());
                        for (BleDevice d : bleDevices) {
                            result.add(d.getMac());
                        }
                        return result;
                    }
                });
            }
        });
    }

    public Single<List<BleDevice>> scan() {
        BleScanRuleConfig config = new BleScanRuleConfig.Builder()
                .setAutoConnect(false).setScanTimeOut(500).build();
        return control.scan(config);
    }

    public Observable<List<BleDevice>> scanCycle() {
        BleScanRuleConfig config = new BleScanRuleConfig.Builder()
                .setAutoConnect(false).setScanTimeOut(0).build();
        return control.scanCycle(config);
    }

    public Observable<String> tryConnect(List<BleDevice> bleDevices) {
        List<BleDevice> temp = new ArrayList<>();
        Iterator<BleDevice> iter;
        BleDevice device;
        List<String> macs = source.getAllMac();
        for (String mac : macs) {
            iter = bleDevices.iterator();
            while (iter.hasNext()) {
                device = iter.next();
                if (device.getMac().equals(mac)) {
                    temp.add(device);
                    iter.remove();
                    break;
                }
            }
        }

        return Observable.fromIterable(temp).flatMap(new Function<BleDevice, ObservableSource<BleStatue>>() {
            @Override
            public ObservableSource<BleStatue> apply(BleDevice device) throws Exception {
                return connect(device).subscribeOn(AndroidSchedulers.mainThread()).toObservable();
            }
        }).observeOn(Schedulers.io()).map(new Function<BleStatue, String>() {
            @Override
            public String apply(BleStatue statue) {
                final String mac = statue.getDevice().getMac();
                byte type = source.getType(mac);
                if (type == BluetoothKey.DEVICE_TYPE_A || type == BluetoothKey.DEVICE_TYPE_B || type == BluetoothKey.DEVICE_TYPE_E) {
                    refreshNotifications(statue);
                }
                return statue.getDevice().getMac();
            }
        });
    }

    public Single<DeviceIndex> scanConnectAndSave(final byte deviceType) {
        BleScanRuleConfig config = new BleScanRuleConfig.Builder()
                .setScanTimeOut(7000).setAutoConnect(false).build();
        return control.scan(config).flatMap(new Function<List<BleDevice>, SingleSource<BleStatue>>() {
            @Override
            public SingleSource<BleStatue> apply(List<BleDevice> devices) {
                if (devices.isEmpty()) {
                    throw new NoScanDevice();
                }
                BleDevice temp = null;
                for (BleDevice bleDevice : devices) {
                    Device info = ProtocolResolution.parseScanRecordData(bleDevice);
                    if (deviceType == BluetoothKey.DEVICE_TYPE_C) {
                        if (info.type == BluetoothKey.DEVICE_TYPE_C || info.type == BluetoothKey.DEVICE_TYPE_C1 || info.type == BluetoothKey.DEVICE_TYPE_C2) {
                            temp = bleDevice;
                            break;
                        }
                    } else if (deviceType == info.type) {
                        temp = bleDevice;
                        break;
                    }
                }
                if (temp == null) {
                    throw new NoScanDevice();
                } else {
                    return connect(temp);
                }
            }
        }).observeOn(Schedulers.io()).map(new Function<BleStatue, DeviceIndex>() {
            @Override
            public DeviceIndex apply(BleStatue statue) {
                final Device info = ProtocolResolution.parseScanRecordData(statue.getDevice());
                if (info == null) {
                    throw new BleException("not parse device info");
                }
                DeviceIndex temp = source.connect(info);
                if (info.type == BluetoothKey.DEVICE_TYPE_A || info.type == BluetoothKey.DEVICE_TYPE_B || info.type == BluetoothKey.DEVICE_TYPE_E) {
                    refreshNotifications(statue);
                }
                if (temp.index == -1) {
                    LogService.getInstance().initAndRefresh(info.mac, (byte) 0x0);
                    if (info.type == BluetoothKey.DEVICE_TYPE_E) {
                        initEPort(statue, info);
                    }
                }
                return temp;
            }
        });
    }

    public void closeScan() {
        control.closeScan();
    }

    /**
     * @param device
     */
    private Single<BleStatue> connect(final BleDevice device) {
        return control.connect(device, this);
    }

    /**
     * @param mac
     */
    public Completable removeDevice(final String mac) {
        return Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) throws Exception {
                BleStatue statue = control.getConnect(mac);
                if (statue != null) {
                    control.disConnect(statue);
                }
                source.deleteDevice(mac);
                emitter.onComplete();
            }
        });
    }

    /**
     * 断开C家族连接
     *
     * @param
     */
    public Completable disConnectC() {
        return Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) throws Exception {
                control.disConnectC();
                emitter.onComplete();
            }
        });
    }

    private void initEPort(final BleStatue statue, final Device device) {
        if (statue != null) {
            Disposable temp = getTFP(statue.getMac(), BluetoothKey.COMMAND_GET)
                    .observeOn(Schedulers.io()).subscribe(new Consumer<DeviceTFP>() {
                        @Override
                        public void accept(DeviceTFP deviceTFP) throws Exception {
                            if (deviceTFP != null && deviceTFP.portList != null) {
                                for (int i = 1; i <= deviceTFP.portList.size(); i++) {
                                    device.name = "Port " + i;
                                    device.port = (byte) i;
                                    source.addDevice(device);
                                    KLog.i("save device:" + device.mac + " port:" + device.port);
                                    LogService.getInstance().initAndRefresh(device.mac, device.port);
                                }
                                refreshNotifications(statue);
                            }
                        }
                    });
            RxSubscriptions.add(temp);
        }
    }

    private void refreshNotifications(final BleStatue statue) {
        Disposable temp = Single.create(new SingleOnSubscribe<List<DeviceInfo>>() {
            @Override
            public void subscribe(@NonNull SingleEmitter<List<DeviceInfo>> emitter) throws Exception {
                emitter.onSuccess(source.getPortInfo(statue.getMac()));
            }
        }).subscribeOn(Schedulers.io()).subscribe(new Consumer<List<DeviceInfo>>() {
            @Override
            public void accept(List<DeviceInfo> deviceInfoList) throws Exception {
                for (final DeviceInfo info : deviceInfoList) {
                    RxSubscriptions.add(control.getNotifications(statue, info.port)
                            .subscribeOn(Schedulers.io())
                            .observeOn(Schedulers.io())
                            .subscribe(new Consumer<List<Notification>>() {
                                @Override
                                public void accept(List<Notification> notifications) {
                                    source.initNotification(statue.getDevice().getMac(), info.port, notifications);
                                    saveModel(statue, info.port);
                                }
                            }));
                }
            }
        });
        RxSubscriptions.add(temp);
    }

    public Single<Boolean> saveNotifications(final String mac, final byte port, final List<Notification> notifications) {
        return Single.create(new SingleOnSubscribe<Boolean>() {
            @Override
            public void subscribe(SingleEmitter<Boolean> emitter) throws Exception {
                source.initNotification(mac, port, notifications);
                emitter.onSuccess(true);
            }
        }).subscribeOn(Schedulers.io());
    }

//    private Completable syncCTime(final BleStatue statue, long delay) {
//        if (delay >0) {
//            return Single.timer(delay, TimeUnit.SECONDS).flatMapCompletable(new Function<Long, CompletableSource>() {
//                @Override
//                public CompletableSource apply(@NonNull Long aLong) throws Exception {
//                    return control.syncCTime(statue)
//                            .subscribeOn(Schedulers.io())
//                            .ignoreElement();
//                }
//            });
//        } else  {
//            return control.syncCTime(statue)
//                    .subscribeOn(Schedulers.io())
//                    .ignoreElement();
//        }
//    }

    public Flowable<List<DeviceInfo>> getInfo() {
        return source.getInfo();
    }

    public Flowable<DeviceName> getDeviceName(String mac, byte port) {
        return source.getDeviceName(mac, port);
    }


    /**
     * @param statue
     */
    private void saveModel(final BleStatue statue, byte port) {
        RxSubscriptions.add(control.initMode(statue, port)
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe(new Consumer<DeviceModel>() {
                    @Override
                    public void accept(DeviceModel model) {
                        source.setModelData(statue.getDevice().getMac(), model);
                        KLog.i("init model success");
                    }
                }));

    }

    public Observable<DeviceModel> getCModel(BleStatue statue) {
        return control.getBroadcast(statue);
    }

    public Single<DeviceMinModel> getModel(BleStatue statue, byte port) {
        return control.getModel(statue, port);
    }

    public Single<DeviceModel> getModel(final String mac, final byte port) {
        return Single.create(new SingleOnSubscribe<DeviceModel>() {
            @Override
            public void subscribe(SingleEmitter<DeviceModel> emitter) throws Exception {
                emitter.onSuccess(source.getModel(mac, port));
            }
        });
    }

    /**
     * @param model
     */
    public Single<Boolean> setModel(BleStatue statue, byte port, DeviceModel model) {
        return control.setModel(statue, port, model);
    }

    public Single<Boolean> setCModel(BleStatue statue, DeviceModel model) {
        return control.setCModel(statue, model);
    }

    /**
     * @param model
     */
    public Single<Boolean> setModel(BleStatue statue, byte port, byte model) {
        return control.setModel(statue, port, model);
    }

    /**
     * @param model
     */
    public Completable saveModel(final String mac, final byte port, final DeviceModel model) {
        return Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) {
                source.setDeviceModel(mac, port, model);
                emitter.onComplete();
            }
        });
    }

    /**
     * @param model
     */
    public void saveModelInfo(final String mac, final DeviceInfo model) {
        Disposable temp = Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) {
                source.setDeviceInfo(mac, model);
                if (model.type == BluetoothKey.DEVICE_TYPE_E && model.portList != null) {
                    for (PortInfo portInfo : model.portList) {
                        source.setPortInfo(mac, portInfo);
                    }
                }
                emitter.onComplete();
            }
        }).subscribeOn(Schedulers.io()).subscribe();
        RxSubscriptions.add(temp);
    }

    public Completable syncTime() {
        return control.syncConnectedTime();
    }

    /**
     * @param command
     */
    public Single<DeviceTFP> getTFP(String mac, byte command) {
        return control.getTFP(mac, command);
    }

    public Observable<DeviceTFP> getTFP(String mac) {
        BleStatue statue = control.getConnect(mac);
        if (statue == null) {
            return Observable.create(new ObservableOnSubscribe<DeviceTFP>() {
                @Override
                public void subscribe(ObservableEmitter<DeviceTFP> emitter) throws Exception {
                    emitter.onError(new BleException("disconnect"));
                }
            });
        }
        return control.getBroadcast(statue).map(new Function<DeviceModel, DeviceTFP>() {
            @Override
            public DeviceTFP apply(@NonNull DeviceModel deviceModel) throws Exception {
                return deviceModel.toTFP();
            }
        });
    }

    public Single<DeviceSetting> getSetting(final String mac, final byte port, byte type, boolean isLocal) {
        if (isLocal) {
            return Single.create(new SingleOnSubscribe<DeviceSetting>() {
                @Override
                public void subscribe(SingleEmitter<DeviceSetting> emitter) throws Exception {
                    emitter.onSuccess(source.getSetting(mac, port));
                }
            });
        } else {
            BleStatue statue = control.getConnect(mac);
            if (statue == null) {
                return Single.create(new SingleOnSubscribe<DeviceSetting>() {
                    @Override
                    public void subscribe(SingleEmitter<DeviceSetting> emitter) throws Exception {
                        emitter.onError(new BleException("disconnect"));
                    }
                });
            } else {
                Single<DeviceSetting> single;
                if (type == BluetoothKey.DEVICE_TYPE_A) {
                    single = control.getSetting(statue);
                } else if (type == BluetoothKey.DEVICE_TYPE_B) {
                    single = control.getBSetting(statue);
                } else if (type == BluetoothKey.DEVICE_TYPE_E) {
                    single = control.getESetting(statue, port);
                } else {
                    single = control.getCSetting(statue);
                }
                return single.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).doOnSuccess(new Consumer<DeviceSetting>() {
                    @Override
                    public void accept(DeviceSetting setting) throws Exception {
                        DeviceSetting temp = source.getSetting(mac, port);
                        setting.name = temp.name;
                        source.setSettings(mac, port, setting);
                    }
                });
            }
        }
    }

    public Single<Boolean> getTempUnit(BleStatue statue) {
        return control.getTempUnit(statue);
    }


    public Single<Boolean> setSetting(final String mac, final byte port, byte type, final DeviceSetting setting) {
        KLog.e("setSetting.....");
        Single<Boolean> single;
        if (type == BluetoothKey.DEVICE_TYPE_A) {
            single = control.setSetting(mac, setting);
        } else if (type == BluetoothKey.DEVICE_TYPE_B) {
            single = control.setBSetting(mac, setting);
        } else if (type == BluetoothKey.DEVICE_TYPE_E) {
            single = control.setESetting(mac, port, setting);
        } else {
            single = control.setCSetting(mac, setting);
        }
        return single.subscribeOn(Schedulers.io()).observeOn(Schedulers.io()).doOnSuccess(new Consumer<Boolean>() {
            @Override
            public void accept(Boolean aBoolean) throws Exception {
                KLog.e("upDataSetting : " + aBoolean);
                if (aBoolean) {
                    source.setSettings(mac, port, setting);
                }
            }
        });
    }

    public Completable setControlTypeByHum(final String mac, final byte port, final boolean isControlTypeByHum) {
        return Completable.create(new CompletableOnSubscribe() {
            @Override
            public void subscribe(CompletableEmitter emitter) {
                source.setControlTypeByHum(mac, port, isControlTypeByHum);
                emitter.onComplete();
            }
        });
    }

    public Completable setControlTypeByHum(final String mac, final boolean isControlTypeByHum) {
        return setControlTypeByHum(mac, (byte) 0x0, isControlTypeByHum);
    }

    public Single<Boolean> isDegree(final String mac) {
//        BleStatue statue = control.getConnect(mac);
//        if (statue != null) {
//            return getSetting(mac,(byte)0x0, (byte)statue.getType(),false).map(new Function<DeviceSetting, Boolean>() {
//                @Override
//                public Boolean apply(@NonNull DeviceSetting setting) throws Exception {
//                    return setting.isDegree;
//                }
//            });
//        } else {
//            return Single.create(new SingleOnSubscribe<Boolean>() {
//                @Override
//                public void subscribe(SingleEmitter<Boolean> emitter) throws Exception {
//                    emitter.onSuccess(source.isDegree(mac));
//                }
//            });
//        }
        BleStatue statue = control.getConnect(mac);
        if (statue != null) {
            return getModel(statue, (byte) 0x0).observeOn(Schedulers.io()).map(new Function<DeviceMinModel, Boolean>() {
                @Override
                public Boolean apply(@NonNull DeviceMinModel deviceMinModel) throws Exception {
                    return deviceMinModel.isDegree;
                }
            });
        }

        return Single.create(new SingleOnSubscribe<Boolean>() {
            @Override
            public void subscribe(SingleEmitter<Boolean> emitter) throws Exception {
                emitter.onSuccess(source.isDegree(mac));
            }
        });
    }

    public Single<Boolean> setChoosePort(final BleStatue statue, final byte port) {
        return control.setChoosePort(statue, port)
                .subscribeOn(Schedulers.io()).observeOn(Schedulers.io())
                .doOnSuccess(new Consumer<Boolean>() {
                    @Override
                    public void accept(Boolean aBoolean) {
                        if (aBoolean) {
//                            source.setChoosePort(statue.getMac(),port);
                        }
                    }
                });
    }

    public void setChoosePort2(BleStatue statue, byte port) {
        Disposable disposable = setChoosePort(statue, port).subscribe();
        RxSubscriptions.add(disposable);
    }
}